﻿using NModbus;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;

namespace ModbusTcpClient
{
    public class ModbusTcp
    {
        private ModbusFactory modbusFactory;
        private IModbusMaster master;
        private TcpClient tcpClient;

        public string IPAdress { get; set; }
        public int Port { get; set; }
        public byte SlaveAddress { get; set; }

        public bool IsConnected
        {
            get
            {
                return tcpClient != null ? tcpClient.Connected : false;
            }
        }

        public ModbusTcp(string ip, int port, byte slaveAddress)
        {
            IPAdress = ip;
            Port = port;
            SlaveAddress = slaveAddress;
        }

        public bool Connect()
        {
            try
            {
                modbusFactory = new ModbusFactory();
                tcpClient = new TcpClient(IPAdress, Port);
                master = modbusFactory.CreateMaster(tcpClient);
                master.Transport.ReadTimeout = 2000;
                master.Transport.Retries = 10;
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public bool Disconnect()
        {
            try
            {
                if (tcpClient.Connected)
                {
                    tcpClient.Close();
                    return true;
                }
                return false;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public bool[] ReadCoils(ushort startAddress, ushort num)
        {
            return master.ReadCoils(SlaveAddress, startAddress, num);
        }

        public bool[] ReadInputs(ushort startAddress, ushort num)
        {
            return master.ReadInputs(SlaveAddress, startAddress, num);
        }

        /// <summary>
        /// 读取单个16bits无符号整数
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        /// <returns>16bits 无符号整数</returns>
        public ushort ReadSingleWord(ushort startAddress)
        {
            return master.ReadHoldingRegisters(SlaveAddress, startAddress, 1)[0];
        }

        /// <summary>
        /// 读取单个32bits整数
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        /// <returns>32bits 整数</returns>
        public int ReadDWord(ushort startAddress)
        {
            ushort[] buf = master.ReadHoldingRegisters(SlaveAddress, startAddress, 2);
            return BitConverter.ToInt32(BitConverter.GetBytes(buf[0] | (uint)(buf[1] << 16)), 0);
        }

        /// <summary>
        /// 读取单个32bits浮点数
        /// </summary>
        /// <param name="startAddress"></param>
        /// <returns>32bits float</returns>
        public float ReadFloat(ushort startAddress)
        {
            ushort[] buf = master.ReadHoldingRegisters(SlaveAddress, startAddress, 2);
            return BitConverter.ToSingle(BitConverter.GetBytes(buf[0] | (uint)(buf[1] << 16)), 0);
        }

        /// <summary>
        /// 读取连续D寄存器
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        /// <param name="num">最大寄存器数:65535</param>
        /// <returns></returns>
        public ushort[] ReadHoldingRegisters(ushort startAddress, int num)
        {
            List<ushort> ans = new List<ushort>();
            if (startAddress < 0 || num <= 0)
            {
                return ans.ToArray();
            }

            // 经测试，nmodbus单次读取最多100个ushort,对于超过100个的读取操作需要分批次读取
            for (int i = 0; i < (num - 1) / 100 + 1; i++)
            {
                ushort[] buf = master.ReadHoldingRegisters(SlaveAddress,
                    (ushort)(startAddress + i * 100),
                    (ushort)((num < (i + 1) * 100) ? (num - i * 100) : 100));
                ans.AddRange(buf.ToList());
            }
            return ans.ToArray();
        }

        public ushort[] ReadInputRegisters(ushort startAddress, ushort num)
        {
            return master.ReadInputRegisters(SlaveAddress, startAddress, num);
        }

        public void WriteSingleCoil(ushort startAddress, bool value)
        {
            master.WriteSingleCoil(SlaveAddress, startAddress, value);
        }

        public void WriteSingleRegister(ushort startAddress, ushort value)
        {
            master.WriteSingleRegister(SlaveAddress, startAddress, value);
        }

        public void WriteMultipleCoils(ushort startAddress, bool[] value)
        {
            master.WriteMultipleCoils(SlaveAddress, startAddress, value);
        }

        /// <summary>
        /// 读取单个16bits无符号整数
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        public void WriteSingleWord(ushort startAddress, ushort value)
        {
            master.WriteMultipleRegisters(SlaveAddress, startAddress, new ushort[] { value });
        }

        /// <summary>
        /// 读取单个32bits整数
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        public void WriteDWord(ushort startAddress, int value)
        {
            ushort[] buf = new ushort[2];
            Buffer.BlockCopy(new int[] { value }, 0, buf, 0, 4);
            master.WriteMultipleRegisters(SlaveAddress, startAddress, buf);
        }

        /// <summary>
        /// 读取单个32bits浮点数
        /// </summary>
        /// <param name="startAddress"></param>
        public void WriteFloat(ushort startAddress, float value)
        {
            ushort[] buf = new ushort[2];
            Buffer.BlockCopy(new float[] { value }, 0, buf, 0, 4);
            master.WriteMultipleRegisters(SlaveAddress, startAddress, buf);
        }

        /// <summary>
        /// 写入连续D寄存器
        /// </summary>
        /// <param name="startAddress">0 对应 寄存器D0</param>
        /// <param name="value"></param>
        public void WriteMultipleRegisters(ushort startAddress, ushort[] value)
        {
            if (startAddress < 0 || value == null || value.Length == 0)
            {
                return;
            }

            // 经测试，nmodbus单次写入最多100个ushort,对于超过100个的读取操作需要分批次读取
            int len = value.Length;
            for (int i = 0; i < ((len - 1) / 100) + 1; i++)
            {
                master.WriteMultipleRegisters(SlaveAddress,
                    (ushort)(startAddress + i * 100),
                    value.Skip(i * 100).Take((len < (i + 1) * 100) ? (len - i * 100) : 100).ToArray());
            }
        }

        public ushort[] ReadWriteMultipleRegisters(ushort startReadAddress,
            ushort numberOfPointsToRead,
            ushort startWriteAddress,
            ushort[] writeData)
        {
            return master.ReadWriteMultipleRegisters(SlaveAddress, startReadAddress, numberOfPointsToRead, startWriteAddress, writeData);
        }


        public void TestForWriteMultipleRegisters(ushort startAddr, int num)
        {
            ushort[] buf = new ushort[num];
            for (int i = 0; i < num; i++)
            {
                buf[i] = (ushort)(i + 1);
            }

            Stopwatch sw = new Stopwatch();
            Console.WriteLine($"写入寄存器数量：{buf.Length}");
            sw.Start();
            WriteMultipleRegisters(startAddr, buf);
            sw.Stop();
            Console.WriteLine($"本次通讯耗时：{sw.ElapsedMilliseconds}ms");
        }

        public void TestForReadWriteMultipleRegisters(ushort startAddr, ushort num, int times)
        {
            ushort[] buf = new ushort[num];
            for (int i = 0; i < num; i++)
            {
                buf[i] = (ushort)(i + 1);
            }

            Console.WriteLine($"读写寄存器数量：{buf.Length}\n读写次数：{times}");

            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < times; i++)
            {
                WriteMultipleRegisters(startAddr, buf);
                buf = ReadHoldingRegisters(startAddr, num);
            }
            sw.Stop();

            Console.WriteLine($"本次通讯耗时：{sw.ElapsedMilliseconds}ms");
        }
    }
}
